/*
 * ACDD Project
 * file BundlesInstaller.java  is  part of ACCD
 * The MIT License (MIT)  Copyright (c) 2015 Bunny Blue,achellies.
 *
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 */
package org.acdd.android.initializer;

import android.app.Application;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.StatFs;
import android.util.Log;
import android.widget.Toast;

import org.acdd.framework.ACDD;
import org.acdd.framework.ACDDConfig;
import org.acdd.log.Logger;
import org.acdd.log.LoggerFactory;
import org.acdd.runtime.RuntimeVariables;

import org.osgi.framework.Bundle;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class BundlesInstaller {
	Logger log = LoggerFactory.getInstance("BundlesInstaller");
	private static boolean isTargetApp;
	private static BundlesInstaller instance;
	BundleDebug bundleDebug;
	private Application application;
	private boolean isInitialized;
	private boolean isDone;

	BundlesInstaller() {
	}

	void init(Application application, BundleDebug bundleDebug, boolean inTargetApp) {

		this.application = application;

		this.bundleDebug = bundleDebug;
		isTargetApp = inTargetApp;
		this.isInitialized = true;
	}

	static synchronized BundlesInstaller getInstance() {
		if (instance != null) {
			return instance;
		}
		synchronized (BundlesInstaller.class) {
			if (instance == null) {
				instance = new BundlesInstaller();
			}

		}
		return instance;
	}

	public synchronized void process(boolean onlyProcessAutos, boolean reInstall) {

		if (!this.isInitialized) {
			Log.e("BundlesInstaller", "Bundle Installer not initialized yet, process abort!");
		} else if (!this.isDone || reInstall) {
			ZipFile zipFile = null;
			try {
				zipFile = new ZipFile(this.application.getApplicationInfo().sourceDir);
				List<String> bundleList = fetchBundleFileList(zipFile, "lib/"+ ACDDConfig.PRELOAD_DIR+"/libcom_", ".so");
				if (bundleList != null && bundleList.size() > 0 && getAvailableSize() < ((long) (((bundleList.size() * 2) * 1024) * 1024))) {
					new Handler(Looper.getMainLooper()).post(new Runnable() {
						public void run() {
							Toast.makeText(RuntimeVariables.androidApplication, "Ops  No Space ", Toast.LENGTH_SHORT).show();
						}
					});
				}
				if (onlyProcessAutos) {
					List bundles = new ArrayList();
					for (String location : bundleList) {
						for (String replace : ACDDConfig.AUTO) {
							if (location.contains(replace.replace(".", "_"))) {
								bundles.add(location);
							}
						}
					}
					processAutoStartBundles(zipFile, bundles, this.application);
				} else {
					installBundle(zipFile, (List) bundleList, this.application);
				}
				if (!reInstall) {
					Utils.updatePackageVersion(this.application);
				}

			} catch (IOException e) {
				Log.e("BundlesInstaller", "IOException while processLibsBundles >>>", e);
			} finally {
				if (zipFile != null) {
					try {
						zipFile.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
				if (reInstall) {
					this.isDone = true;
				}

			}
			if (reInstall) {
				this.isDone = true;
			}
		}
	}

	private List<String> fetchBundleFileList(ZipFile zipFile, String prefix, String suffix) {
		List<String> arrayList = new ArrayList<String>();
		try {
			Enumeration<?> entries = zipFile.entries();
			while (entries.hasMoreElements()) {
				String name = ((ZipEntry) entries.nextElement()).getName();
				if (name.startsWith(prefix) && name.endsWith(suffix)) {
					arrayList.add(name);
				}
			}
		} catch (Throwable e) {
			Log.e("BundlesInstaller", "Exception while get bundles in assets or lib", e);
		}
		return arrayList;
	}

	private long getAvailableSize() {

		StatFs statFs = new StatFs(Environment.getDataDirectory().getPath());
		return ((long) statFs.getAvailableBlocks()) * ((long) statFs.getBlockSize());
	}

	public void processAutoStartBundles(ZipFile zipFile, List<String> bundles, Application application) {

		for (String location : bundles) {
			installBundle(zipFile, location, application);
		}
		if (isTargetApp) {
			for (String location : ACDDConfig.AUTO) {
				Bundle bundle = ACDD.getInstance().getBundle(location);
				if (bundle != null) {
					try {
						bundle.start();
					} catch (Throwable e) {
						Log.e("BundlesInstaller", "Could not auto start bundle: " + bundle.getLocation(), e);
					}
				}
			}
		}
	}

	private void installBundle(ZipFile zipFile, List<String> bundles, Application application) {
		for (String location : ACDDConfig.DELAY) {
			String mLocation = contains(bundles, location.replace(".", "_"));
			if (mLocation != null && mLocation.length() > 0) {
				installBundle(zipFile, mLocation, application);
				bundles.remove(mLocation);
			}
		}
		for (String location : bundles) {
			installBundle(zipFile, location, application);
		}
		if (isTargetApp) {
			String[] auto = ACDDConfig.AUTO;
			for (String location:auto){
				Bundle bundle = ACDD.getInstance().getBundle(location);
				if (bundle != null) {
					try {
						bundle.start();
					} catch (Throwable e) {
						Log.e("BundlesInstaller", "Could not auto start bundle: " + bundle.getLocation(), e);
					}
				}
			}

		}
	}

	private String contains(List<String> bundles, String location) {

		if (bundles == null || location == null) {
			return null;
		}
		for (String mLocation : bundles) {
			if (mLocation.contains(location)) {
				return mLocation;
			}
		}
		return null;
	}

	private boolean installBundle(ZipFile zipFile, String location, Application application) {

		log.info("processLibsBundle entryName " + location);
		this.bundleDebug.installExternalBundle(location);
		String fileNameFromEntryName = Utils.getFileNameFromEntryName(location);
		String packageNameFromEntryName = Utils.getPackageNameFromEntryName(location);
		if (packageNameFromEntryName == null || packageNameFromEntryName.length() <= 0) {
			return false;
		}
		File archvieFile = new File(new File(application.getFilesDir().getParentFile(), "lib"), fileNameFromEntryName);
		if (ACDD.getInstance().getBundle(packageNameFromEntryName) != null) {
			return false;
		}
		try {
			if (archvieFile.exists()) {
				ACDD.getInstance().installBundle(packageNameFromEntryName, archvieFile);
			} else {
				ACDD.getInstance().installBundle(packageNameFromEntryName, zipFile.getInputStream(zipFile.getEntry(location)));
			}
			log.info("Succeed to install bundle " + packageNameFromEntryName);
			return true;
		} catch (Throwable e) {
			Log.e("BundlesInstaller", "Could not install bundle.", e);
			return false;
		}
	}
}
